var express = require("express");
var router = express.Router();
var dayjs = require("dayjs");
var { v1: uuidv1 } = require("uuid");

const aedes = require("aedes")({
  heartbeatInterval: 60000, //60s发送一次心跳包
  connectTimeout: 120000, //如果与服务器120s没有收到连接客户端发过来的心跳包，则视为连接断开
});

const httpServer = require("http").createServer();
const ws = require("websocket-stream");
const port = 1883;

ws.createServer({ server: httpServer }, aedes.handle);

var messageState = {
  list: [],
  timeoutFn: null,
};

var onlineState = {
  list: [],
};

// 启动服务器
httpServer.listen(port, function () {
  console.log("mqtt服务启动成功", `mqtt://127.0.0.1:${port}`);
});

//验证账号密码
aedes.authenticate = async function (client, username, password, callback) {
  // client.id是客户端的id，是唯一的，username是客户端的用户名（密码为buffer，需要转化为string），password是客户端的密码
  // 我们可以在这里进行用户的身份验证，是否允许客户端的这次连接请求
  // callback函数需要传递两个参数，第一个是错误实例，第二个是是否同意连接
  if (username === "admin" && password.toString() === "123456") {
    callback(null, true);
  } else {
    callback(null, false);
  }
};

// 监听MQTT服务器端口，当有客户端主动断开连接或者服务器120s内没收到某个客户端的心跳包就会触发该回调
aedes.on("clientDisconnect", async (client) => {
  const [visitorId, accountId] = client.id.split("-");

  let idx = onlineState.list.findIndex(
    (v) => v.visitorId == visitorId && v.accountId == accountId
  );
  console.log(
    "客户端主动断开连接",
    "clientDisconnect:",
    client.id,
    visitorId,
    accountId,
    idx
  );

  if (idx != -1) {
    onlineState.list[idx].status = 0;
    onlineState.list[idx].endTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    publishOnline({ data: onlineState.list });
  }
});

// 监听MQTT服务器端口，当有客户端连接上时，触发该回调
aedes.on("client", async (client) => {
  const [visitorId, accountId, now] = client.id.split("-");

  console.log(
    "有客户端连接上",
    "client:",
    client.id,
    visitorId,
    accountId,
    now,
    messageState.list
  );

  let idx = onlineState.list.findIndex(
    (v) => v.visitorId == visitorId && v.accountId == accountId
  );

  if (idx == -1) {
    onlineState.list.push({
      visitorId,
      accountId,
      startTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
      endTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
      status: 1,
    });
  } else {
    if (onlineState.list[idx].status == 0) {
      onlineState.list[idx].status = 1;
    }
  }

  setTimeout(() => {
    publishMessage({ data: messageState.list });
    publishOnline({ data: onlineState.list });
  }, 100);
});

// 处理收到的消息,我们订阅所有主题收到的消息都可以通过这个事件获取(我们可以把订阅收到消息的处理函数写在上面订阅主题函数的第二个参数里面，或者统一写在下面)
aedes.on("publish", async function (packet, client) {
  // packet.topic表示哪个主题，packet.payload是收到的数据，是一串二进制数据，我们需要用.toString()将它转化为字符串
  console.log("收到消息", "publish", packet.topic, packet.payload.toString());

  if (packet.topic === "client") {
    let itemData = [
      {
        ...JSON.parse(packet.payload.toString()),
        id: uuidv1(6),
        pushTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
      },
    ];

    publishMessage({ data: itemData });

    messageState.list = messageState.list.concat(itemData);

    resetMessage();
  }
});

// 推送当前人员
const publishOnline = ({ data }) => {
  aedes.publish(
    {
      topic: "online", // 发布主题
      payload: typeof data === "string" ? data : JSON.stringify(data) || "", // 消息内容
      qos: 1, // MQTT消息的服务质量（quality of service）。服务质量是1，这意味着这个消息需要至少一次确认（ACK）才能被认为是传输成功
      retain: false, // MQTT消息的保留标志（retain flag），它用于控制消息是否应该被保留在MQTT服务器上，以便新的订阅者可以接收到它。保留标志是false，这意味着这个消息不应该被保留
      cmd: "publish", // MQTT消息的命令（command），它用于控制消息的类型。命令是"publish"，这意味着这个消息是一个发布消息
      dup: false, // 判断消息是否是重复的
    },
    (err) => {
      // 发布失败的回调
      if (err) console.log("发布失败");
    }
  );
};

// 推送消息
const publishMessage = ({ data }) => {
  aedes.publish(
    {
      topic: "message", // 发布主题
      payload: typeof data === "string" ? data : JSON.stringify(data) || "", // 消息内容
      qos: 1, // MQTT消息的服务质量（quality of service）。服务质量是1，这意味着这个消息需要至少一次确认（ACK）才能被认为是传输成功
      retain: false, // MQTT消息的保留标志（retain flag），它用于控制消息是否应该被保留在MQTT服务器上，以便新的订阅者可以接收到它。保留标志是false，这意味着这个消息不应该被保留
      cmd: "publish", // MQTT消息的命令（command），它用于控制消息的类型。命令是"publish"，这意味着这个消息是一个发布消息
      dup: false, // 判断消息是否是重复的
    },
    (err) => {
      // 发布失败的回调
      if (err) console.log("发布失败");
    }
  );
};

const resetMessage = () => {
  clearInterval(messageState.timeoutFn);
  messageState.timeoutFn = null;
  messageState.timeoutFn = setTimeout(() => {
    messageState.list = [];
    console.log("清空================");
  }, 1000 * 60 * 30);
};

// 客户端mqtt配置
router.get("/config", (req, res) => {
  const config = {
    connectTimeout: 4000, // 超时时间
    clientId: Date.now(), //不填默认随机生成一个ID
    username: "admin", //用户名
    password: "123456", //密码
  };

  res.send({
    code: 0,
    msg: "成功",
    data: config,
  });
});

module.exports = router;
